﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Xml.Serialization;
using System.Reflection;

namespace Vibz.Common.Channel
{
    public class SocketServer : SocketPublisher, IDisposable
    {
        static IPAddress ip;
        private const int MaxLengthOfPendingConnectionsQueue = 10;
        public List<Client> Clients = new List<Client>();
        public string ConnectionPath { get; set; }
        public SocketServer()
        {
            ip = IPAddress.Parse(Network.GetIp());
            Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            Socket.Bind(new IPEndPoint(ip, 0));
            Socket.Listen(MaxLengthOfPendingConnectionsQueue);
            Socket.BeginAccept(OnConnectRequest, null);

            ConnectionPath = ip.ToString() + ":" + ((IPEndPoint)Socket.LocalEndPoint).Port.ToString();
        }
        public SocketServer(System.Net.Sockets.Socket socket)
        {
            Socket = socket;
        }

        public event OnConnection ConnectionRequested;
        public delegate void OnConnection(System.Net.Sockets.Socket socket, EventArgs e);
        void OnConnectRequest(IAsyncResult asyn)
        {
            try
            {
                var dataTransferSocket = Socket.EndAccept(asyn);
                WaitForData(dataTransferSocket);
                Socket.BeginAccept(OnConnectRequest, null);
                if (ConnectionRequested != null)
                {
                    ConnectionRequested(dataTransferSocket, null);
                }

                Clients.Add(new Client(dataTransferSocket));
            }
            catch (ObjectDisposedException)
            {
                Console.WriteLine("OnConnectRequest: Socket has been closed.");
            }
            catch (SocketException ex)
            {
                Console.WriteLine(
                   string.Format("Something fishy happened: {0}", ex.Message));
                throw;
            }
        }
        static object _lock = new object();
        public void Broadcast(object obj)
        {
            MethodInfo method = this.GetType().GetMethod("Broadcast", BindingFlags.Public | BindingFlags.Static);
            MethodInfo genericMethod = method.MakeGenericMethod(obj.GetType());
            genericMethod.Invoke(this, new object[] { obj, Clients });
        }
        public static void Broadcast<T>(T data, List<Client> Clients)
        {
            lock (_lock)
            {
                try
                {
                    int cntClients = Clients.Count;
                    for (int i = 0; i < cntClients; i++)
                    {
                        Client skt = Clients[i];
                        try
                        {
                            skt.SendMessage<T>(data);
                        }
                        catch (SocketException exc)
                        {
                            Console.WriteLine("Lost connection with " + skt.Name + ". " + exc.Message);
                            Clients.Remove(skt);
                        }
                    }
                }
                catch (Exception ex)
                {
                    throw new Exception("Error in broadcasting: ", ex);                
                }
            }
        }

        internal override bool ResolveDataReciept(System.Net.Sockets.Socket socket, string receivedData)
        {
            bool retValue = false;
            if (receivedData.StartsWith("#") && receivedData.EndsWith("#"))
            {
                byte[] retMessage = GetMessageBytes(receivedData.Substring(1, receivedData.Length - 2));
                Network.SendMessageToClient(socket, retMessage);
                retValue = true;
            }
            return retValue;
        }
        public event OnDataRequest DataRequested;
        public delegate byte[] OnDataRequest(EventArgs e);
        private byte[] GetMessageBytes(string key)
        {
            byte[] retValue = null;
            switch (key)
            {
                case "INIT":
                    if (DataRequested != null)
                    {
                        retValue = DataRequested(null);
                    }   
                    break;
            }
            return retValue;
        }

        public void Dispose()
        {
            Disconnect();
        }

        private void Disconnect()
        {
            if (Socket != null)
            {
                Socket.Shutdown(SocketShutdown.Both);
                Socket.Close();
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }
        }
    }
}
